//
//  BleManager.swift
//  EPHome
//
//  Created by liujianlong on 2022/8/2.
//

import UIKit
import CoreBluetooth

internal func kMyPrint<T>(_ msg:T,file:String = #file, funcName:String = #function, lineNumber:Int = #line){
#if DEBUG
    let fileName = (file as NSString).lastPathComponent
    let formmater = DateFormatter()
    formmater.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSS"
    let time =  formmater.string(from:Date())
    print("\(fileName)(\(lineNumber))-\(time):\(msg)")
#endif
}

public struct WriteTask{
    public var name : String?
    public var data : Data
    public var char : CBCharacteristic
    public var type: CBCharacteristicWriteType
    public var completion : ((WriteTask)->Void)?
}

@objc public enum BLEStatus : Int{
    case normal = 0
    case disconnect = 1
    case connecting = 2
    case connectedSucces = 3
    case connectedFail = 4
    case reConnecting = 5
    case didDiscoverCharacteristics = 6
    case DFU = 7
}

///蓝牙状态的通知
public let BLEStatusUpdateNotifyName = "BLEStatusUpdateNotifyName"
private let lastConnectedUUIDKey = "lastConnectedUUIDKey"
public typealias scanResult = ((_ array:[CBPeripheral])->Void)
public typealias scanOneByOneResult = ((CBPeripheral)->Void)
public typealias connectResult = ((_ status:BLEStatus)->Void)
public typealias scanFilter = ((_ peripheral:CBPeripheral )->Bool)

//MARK: - 蓝牙中心管理
public let BLE = CCBleManager.share
@objcMembers public class CCBleManager : NSObject {
    static let share = CCBleManager()
    private override init() {}
    /// 扫描到的外围设备
    private(set) public var searchedPeripherals:[CBPeripheral] = [CBPeripheral](){
        didSet{
            updateScanBlock?(searchedPeripherals)
            if searchedPeripherals.count > 0, let per = searchedPeripherals.last{
                updateScanOneByOneBlock?(per)
            }
        }
    }
    /// 连接成功的外围设备
    private(set) public var connectedPeripherals:[CBPeripheral] = [CBPeripheral](){
        didSet{
            updateScanBlock?(searchedPeripherals)
            for per in connectedPeripherals{
                updateScanOneByOneBlock?(per)
            }
        }
    }
    ///app上一次启动连接过外设的uuid
    private var lastConnectedUUID = UserDefaults.standard.value(forKey: lastConnectedUUIDKey) as? String ?? ""{
        didSet{
            UserDefaults.standard.set(lastConnectedUUID, forKey: lastConnectedUUIDKey)
            UserDefaults.standard.synchronize()
        }
    }
    /// 任务队列
    private var writeQueue = [WriteTask]()
    /// 当前是否是写入状态
    private var isWriting = false
    /// 任务队列里，两个任务写入的间隔时间
    public var delayWrite = 0.01
    /// 重连定时器
    private var reConnectTimer = DispatchSource.makeTimerSource(queue:.main)
    ///扫描持续时间
    public var scanDuration = 15
    ///最大支持同时连接外设的数量
    public var maxConnected = 1
    /// 按照厂商服务搜索
    public var scanWithServices = [String](){
        didSet{
            let array = scanWithServices.compactMap{ element in
                return !services.contains{$0.uuidString == element} ? CBUUID(string: element) : nil
            }
            services += array
        }
    }
    /// 服务
    private var services = [CBUUID]()
    ///连接的外设有多少个services
    private var servicesCount = 0
    ///扫描block返回扫描到的数组
    private var updateScanBlock:scanResult?
    ///扫描block返回当前扫描的外设
    private var updateScanOneByOneBlock:scanOneByOneResult?
    ///绑定/解绑 block
    public var connectBlock:connectResult?
    ///其他筛选条件
    public var scanWithOther:scanFilter?
    
    public var delegate : CCBLEData_update_protocol?
    lazy public var central : CBCentralManager = {
        return CBCentralManager(delegate:self, queue: DispatchQueue.main)
    }()
    
    public var status : BLEStatus = .normal{
        didSet{
            DispatchQueue.main.async {[weak self] in
                self?.connectBlock?(self?.status ?? .normal)
                NotificationCenter.default.post(name:NSNotification.Name(BLEStatusUpdateNotifyName), object:nil)
            }
        }
    }
    
    
    /// 开始扫描<返回整个扫描到数组>
    public func startScan(result:scanResult?){
        updateScanBlock = result
        setupScan()
    }
    
    /// 开始扫描<返回当前扫描到外设>
    public func startScanOneByOne(result:scanOneByOneResult?){
        updateScanOneByOneBlock = result
        setupScan()
    }
    
    private func setupScan(){
        searchedPeripherals = connectedPeripherals
        central.delegate = self
        if central.state == .poweredOn{
            /// 第一个参数：是否重复扫描已经发现的设备。如果需要不断获取最新信号强度RSSI，此参数要设为YES
            /// 第二个参数：蓝牙没打开，是否弹出提示框
            let option = [CBCentralManagerScanOptionAllowDuplicatesKey:false,
                               CBCentralManagerOptionShowPowerAlertKey:true]
            central.scanForPeripherals(withServices:self.services, options:option)
        }
        
        ///x秒后停止扫描
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(scanDuration)) {[weak self] in
            self?.stopScan()
        }
    }
    
    /// 停止扫描
    public func stopScan(){
        central.stopScan()
    }
    
    /// 连接指定的外围设备，通过MAC地址
    public func connect(_ peripheral:CBPeripheral?=nil,_ mac:String?=nil, _ block:connectResult?=nil){
        guard let p = map(peripheral,mac), p.state != .connected, p.state != .connecting   else{
            block?(status)
            return
        }
        if let block_new = block{
            connectBlock = block_new
        }
        /// 第一个参数：在程序被挂起时，连接成功显示alert提醒框
        /// 第二个参数：在程序被挂起时，断开连接显示alert提醒框
        /// 第三个参数:   在程序被挂起时，显示所有的提醒消息
        let option = [CBConnectPeripheralOptionNotifyOnConnectionKey:false,
                   CBConnectPeripheralOptionNotifyOnDisconnectionKey:false,
                    CBConnectPeripheralOptionNotifyOnNotificationKey:false]
        central.connect(p, options:option)
    }
    
    /// 断开指定外围设备，通过MAC地址
    public func disConnect(_ peripheral:CBPeripheral?=nil,_ mac:String?=nil, _ block:connectResult?=nil){
        guard let p = map(peripheral,mac) else{
            block?(status)
            return
        }
        if let block_new = block{
            connectBlock = block_new
        }
        central.cancelPeripheralConnection(p)
        connectedPeripherals.removeAll{$0 == p}

        ///主动断开，连接记录也重新生成
        if self.status != .DFU{
            lastConnectedUUID = connectedPeripherals.map{String(format:"%@|",$0.identifier.uuidString)}.joined()
        }
    }
    
    ///按照mac和peripheral 选出具体的设备
    private func map(_ peripheral:CBPeripheral?=nil,_ mac:String?=nil)->CBPeripheral?{
        if peripheral == nil && (mac == nil || mac == ""){
            return nil
        }else if mac != nil && mac != ""{
            return searchedPeripherals.filter{$0.mac == mac}.first
        }else {
            return  peripheral
        }
    }
    
    ///重连上次连接的
    private func reConnect(){
        /// 1, 如果已连接的外设数组，有值，则先连接。
        let array = lastConnectedUUID.components(separatedBy: "|")
        ///如果已连接的外围设备，大于等于最大连接数量，则不进行下一步动作
        if self.connectedPeripherals.count >= self.maxConnected{return}
        for per in searchedPeripherals{
            if array.contains(per.identifier.uuidString){
                //                kMyPrint("========\(connectedPeripherals.count)  num=\(maxConnected)")
                connect(per)
            }
        }
    }
    
    
    /// 获取系统底层连接的外围设备，系统底层连接时候，有可能app搜索不到该设备
    private func systemConnected(){
        let array = central.retrieveConnectedPeripherals(withServices:services)
        let unconnectArray = array.filter{$0.state == .disconnected || $0.mac.count <= 0}
        let temp = unconnectArray.filter{!searchedPeripherals.contains($0)}
        if temp.count > 0 {
            searchedPeripherals += temp
        }
    }
    
    ///定时器方法
    private func timerAction(){
        reConnectTimer.setEventHandler(handler:{[weak self] in
            self?.systemConnected()
            self?.reConnect()
        })
        reConnectTimer.schedule(deadline: .now(), repeating: 1)
        reConnectTimer.resume()
    }
}


//MARK: - CCBleManager写入数据，任务队列，任务完成回调
extension CCBleManager{
    /// - Parameters:加入任务队列
    ///   - data: 写入的数据
    ///   - service_uuid: 服务ID
    ///   - characteristic_uuid: 特征ID
    ///   - type: 写入类型
    ///   - completion: 写入回调
    public func write(data:Data, service_uuid:String, characteristic_uuid:String,type:CBCharacteristicWriteType? = .withoutResponse, completion:((WriteTask)->Void)?=nil)->WriteTask?{
        
        var task : WriteTask?
        
        guard connectedPeripherals.count > 0, let peripheral = connectedPeripherals.first else{
            //kMyPrint("没有链接任何外设 = \(connectedPeripherals)")
            DispatchQueue.main.asyncAfter(deadline:.now() + 1) {[weak self] in
                task = self?.write(data: data, service_uuid: service_uuid, characteristic_uuid: characteristic_uuid,type:type, completion: completion)
            }
            return task ?? nil
        }
        
        guard let service = peripheral.services?.filter({$0.uuid.uuidString == service_uuid}).first else{
            let array = peripheral.services?.map{$0.uuid.uuidString} ?? [String]()
            kMyPrint("没有找到对应的服务 = \(service_uuid) from : \(array)")
            DispatchQueue.main.asyncAfter(deadline:.now() + 1) {[weak self] in
                task = self?.write(data: data, service_uuid: service_uuid, characteristic_uuid: characteristic_uuid,type:type, completion: completion)
            }
            return task ?? nil
        }
        
        guard let char = service.characteristics?.filter({$0.uuid.uuidString == characteristic_uuid}).first else{
            let array = peripheral.services?.map{$0.uuid.uuidString} ?? [String]()
            kMyPrint("没有找到对应的特征 = \(characteristic_uuid) from : \(array)")
            DispatchQueue.main.asyncAfter(deadline:.now() + 1) {[weak self] in
                task = self?.write(data: data, service_uuid: service_uuid, characteristic_uuid: characteristic_uuid,type:type, completion: completion)
            }
            return task ?? nil
        }
        
        // 设置时间格式
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
        let dateString = dateFormatter.string(from: Date())
        
        task = WriteTask(name:"创建任务时间："+dateString,
                         data: data,
                         char: char,
                         type: type ?? .withoutResponse,
                         completion: completion)
        writeQueue.append(task!)
        sendStatus(0, task)
        if writeQueue.count <= 1{
            processNextTask()
        }
        return task!
    }
    
    /// 任务队列开始执行
    private func processNextTask() {
        guard connectedPeripherals.count > 0, let peripheral = connectedPeripherals.first else{
            kMyPrint("没有链接任何外设")
            return}
        guard !isWriting else {
            DispatchQueue.main.asyncAfter(deadline:.now() + 1) {[weak self] in
                self?.processNextTask()
            }
            return }
        guard let task = writeQueue.first else { return }
        
        
        sendStatus(1, task)
        isWriting = true
        switch task.type {
        case .withResponse:
            peripheral.writeValue(task.data, for: task.char, type: .withResponse)
        case .withoutResponse:
            peripheral.writeValue(task.data, for: task.char, type: .withoutResponse)
            taskCompleted()
        @unknown default:
            break
        }
    }
    
    /// 当前任务执行完成，删除
    private func taskCompleted() {
        if !writeQueue.isEmpty, let task = writeQueue.first {
            task.completion?(task)
            sendStatus(2, task)
            DispatchQueue.main.asyncAfter(deadline:.now() + delayWrite) {[weak self] in
                guard let self = self else{return}
                self.isWriting = false
                self.writeQueue.removeFirst()
                self.processNextTask()
            }
        }
        
    }
    
    private func sendStatus(_ status:Int, _ task:WriteTask?){
        let name = task?.name ?? ""
        let hex = [UInt8](task?.data ?? Data()).map{String(format: "%02X", $0)}.joined(separator: ", ")
        let char_uuid = task?.char.uuid.uuidString ?? ""
        let type = task?.type == .withResponse ? "withResponse" : "withoutResponse"
        var str = ""
        switch status{
        case 0:
            str = "加入队列"
        case 1:
            str = "开始发送"
        case 2:
            str = "发送完成"
        default:
            break
        }
        kMyPrint("=========================================================\n task \(str) = \(name), \n char = \(char_uuid), \n type = \(type), \n hex = \(hex) \n")
        
    }
}



//MARK: - CBCentralManagerDelegate
extension CCBleManager : CBCentralManagerDelegate{
    
    public func centralManagerDidUpdateState(_ central: CBCentralManager) {
        DispatchQueue.main.async { [self] in
            self.status = self.status == .DFU ? self.status : .normal
            self.searchedPeripherals.removeAll()
            switch central.state {
            case .unknown:
                break
            case .resetting:
                break
            case .unsupported:
                break
            case .unauthorized:
                break
            case .poweredOn:
                self.timerAction()
                self.startScan(result:self.updateScanBlock)
                self.startScanOneByOne(result: self.updateScanOneByOneBlock)
            case .poweredOff:
                self.reConnectTimer.suspend()
                self.connectedPeripherals.removeAll()
                self.status = .connectedFail
            default:
                break
            }
        }
    }
    
    ///搜索到了设备
    public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        
        ///1.获取外围设备的mac
        let data = advertisementData["kCBAdvDataManufacturerData"] as? NSData ?? NSData()
        peripheral.advertData = data
        peripheral.mac = self.tranToMac(data)
        peripheral.kRSSI = RSSI.intValue
        kMyPrint("搜索到=======\(peripheral.name ?? "") mac  = \(peripheral.mac)  rssi = \(RSSI.intValue)")
        
        
        ///2 通过其他规则，过滤/筛选设备
        let fitler = self.scanWithOther?(peripheral) ?? true
        ///2.1, 如果不符合过滤/筛选规则，则忽略
        if !fitler{return}
        
        ///3.如果包含在已经搜索的列表里，则替换
        if let index = searchedPeripherals.firstIndex(where: { $0.identifier == peripheral.identifier }) {
            searchedPeripherals[index] = peripheral
        } else {
            searchedPeripherals.append(peripheral)
        }
    }
    
    ///连接失败
    public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        DispatchQueue.main.async {
            kMyPrint("-------------连接失败")
            self.status = .connectedFail
            self.disConnect(peripheral,nil,self.connectBlock)
        }
    }
    /// 断开连接
    public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        DispatchQueue.main.async {
            kMyPrint("-------------断开链接  per  =  \(peripheral)   |  error = \(error)")
            self.status = self.status == .DFU ? self.status : .disconnect
            ///自主断开，error不会是nil
            if self.searchedPeripherals.contains(peripheral) && error != nil{
                self.searchedPeripherals.removeAll{$0 == peripheral}
            }
            if self.connectedPeripherals.contains(peripheral){
                self.connectedPeripherals.removeAll{$0 == peripheral}
            }
        }
    }
    
    /// 连接成功
    public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        DispatchQueue.main.async {
            kMyPrint("-------------连接成功 \(peripheral)  mac = \(peripheral.mac)")
            //central.stopScan()
            peripheral.delegate = self
            peripheral.discoverServices(nil)///查找所有服务
            
            ///1，如果已连接的外围设备，大于等于最大连接数量，则自动断开最早连接的外围设备
            if self.connectedPeripherals.count >= self.maxConnected && !self.connectedPeripherals.contains(peripheral){
                self.disConnect(self.connectedPeripherals.first!)
            }
            //            ///2，等到所有服务和特征搜索完毕，再返回，否则在连接成功的回调里，立马发送协议，可能这时候服务特征都没搜索完毕，发送协议会不起作用
            //            self.status = self.status == .DFU ? self.status : .connectedSucces
//            if self.connectedPeripherals.contains(peripheral) == false && self.status != .DFU{///这里要注释掉，否则nordic的ota，会进入dfu模式，不会添加到连接外设的数组里
            if self.connectedPeripherals.contains(peripheral) == false {
                self.connectedPeripherals.append(peripheral)
                if self.status != .DFU {
                    self.lastConnectedUUID = self.connectedPeripherals.map{String(format:"%@|",$0.identifier.uuidString)}.joined()
                }
            }
            
            //            ///3，开始任务队列
            //            self.processNextTask()
        }
    }
}

//MARK: - CBPeripheralDelegate
extension CCBleManager:CBPeripheralDelegate
{
    ///搜索到服务
    public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if error != nil {
            kMyPrint("\n\n=====didDiscoverServices错误：\(error!)")
            return
        }
        servicesCount = 0
        for service in peripheral.services!{
            print("+++++++++++++++++ 服务 = \(service.uuid.uuidString)")
            servicesCount += 1
            peripheral.discoverCharacteristics(nil, for: service)///查找特征值
        }
    }
    
    ///搜索到特征值
    public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if error != nil {
            kMyPrint("\n\n=====didDiscoverCharacteristicsFor错误：\(error!)")
            return
        }
        var array = [[String:CBCharacteristic]]()
        for char in service.characteristics!{
            kMyPrint("服务 = \(service.uuid.uuidString) +++++++++++++++++ 特征 = \(char.uuid.uuidString), char.properties = \(char.properties)")
            array.append([char.uuid.uuidString:char])
            ///这里要 注释掉，因为有的芯片的监听通道，属性不是notify，比如富芮坤的ota的监听通道FF02。
//            if char.properties == CBCharacteristicProperties.notify{
                peripheral.setNotifyValue(true, for: char)///订阅, 实时接收
//            }
        }
        peripheral.requestChar[service.uuid.uuidString] = array
        
        servicesCount -= 1
        if servicesCount <= 0{
            ///2，等到所有服务和特征搜索完毕，再返回，否则在连接成功的回调里，立马发送协议，可能这时候服务特征都没搜索完毕，发送协议会不起作用
            MyPrint("==============> status  =  \(self.status.rawValue)")
            self.status = self.status == .DFU ? self.status : .connectedSucces
            ///3，开始任务队列
            self.processNextTask()
        }
    }
    
    
    /// 设置通知的回调
    public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {
            kMyPrint("\n\n=====didUpdateNotificationStateFor错误：\(error!)")
        }
    }
    
    ///写数据回调
    public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {
            kMyPrint("\n\nchar=\(characteristic.uuid.uuidString)=====didWriteValueFor错误：\(error!)")
        }
        taskCompleted()
    }
    ///数据更新
    public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {
            kMyPrint("\n\n=====didUpdateValueFor错误：\(error!)")
            return
        }
        guard let delegate = BLE.delegate else{
            let bytes = [UInt8](characteristic.value ?? Data())
            let str = bytes.map{String(format: "%02X", $0)}.joined(separator:" |")
            if bytes.count < 40 {
                print("数据更新 = \(str)")
            }
            //            print("数据更新 = \(str)")
            return
        }
        delegate.handleData?(peripheral, didUpdateValueFor: characteristic, error: error)
    }
}


//MARK: - CBPeripheral扩展
private var macKey = "macKey"
private var advertDataKey = "advertDataKey"
private var requestCharKey = "requestCharKey"
private var kRSSIKey = "kRSSIKey"
public extension CBPeripheral{
    @objc var mac : String{
        set {objc_setAssociatedObject(self, &macKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)}
        get{return objc_getAssociatedObject(self, &macKey) as? String ?? ""}
    }
    
    @objc var kRSSI : Int{
        set {objc_setAssociatedObject(self, &kRSSIKey, newValue, .OBJC_ASSOCIATION_ASSIGN)}
        get{return objc_getAssociatedObject(self, &kRSSIKey) as? Int ?? -100}
    }
    
    @objc var advertData : NSData{
        set {objc_setAssociatedObject(self, &advertDataKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)}
        get{return objc_getAssociatedObject(self, &advertDataKey) as? NSData ??  NSData()}
    }
    
    @objc var requestChar : [String : [[String:CBCharacteristic]]]{
        set {objc_setAssociatedObject(self, &requestCharKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)}
        get{return objc_getAssociatedObject(self, &requestCharKey) as? [String : [[String:CBCharacteristic]]] ??  [String : [[String:CBCharacteristic]]]()}
    }
}

extension CCBleManager{
    func tranToMac(_ data:NSData)->String{
        let bytes = [UInt8](data) //byte数组
        var str = ""
        for i in 0..<bytes.count {
            str += String(format:"%02lX:", bytes[i])
        }
        if str.count < 22 {return ""}
        let mac = str[6...22]
        return mac
    }
}

